/**
 * 
 */
package com.gragra.sampling.nonproj.structures.nonprojective.singleroot;
import java.util.BitSet;
import java.util.concurrent.ConcurrentLinkedQueue;
import com.gragra.sampling.PopulationProbabilityAssigner;
import com.gragra.sampling.ThreadedUniformAccess;
import com.gragra.sampling.binary.ConvertSample;
import com.gragra.sampling.sequentialStructure.WordAndTagHolder;
/**
 * @author Christoph Teichmann
 * 15.10.2013
 */
public class GibbsGibbsSingleRootNonProjDepdTree extends
		MHMHSingleRootNonProjDepdTree
{
	/**
	 * 
	 */
	private final static ConcurrentLinkedQueue<double[]> probsPool = new ConcurrentLinkedQueue<double[]>();
	/**
	 * 
	 * @return
	 */
	private double[] getProbsFromPool(int size)
	{
		double[] probs = probsPool.poll();
		if(probs == null || probs.length < size)
		{probs = new double[size];}
		return probs;
	}
	/**
	 * 
	 * @param size
	 */
	private void returnProbsToPool(double[] probs)
	{probsPool.add(probs);}
	/**
	 * @param wth
	 * @param threads
	 * @param cs
	 */
	public GibbsGibbsSingleRootNonProjDepdTree(WordAndTagHolder wth,
			int threads, ConvertSample cs)
	{super(wth, threads, cs);}
	/* (non-Javadoc)
	 * @see com.gragra.sampling.nonproj.structures.nonprojective.NonProjDepdTree#resampleAtPosition(int, com.gragra.sampling.PopulationProbabilityAssigner, double, int, com.gragra.sampling.ThreadedUniformAccess)
	 */
	@Override
	protected void resampleAtPosition(int pos,
			PopulationProbabilityAssigner pa, double annealing, int thread,
			ThreadedUniformAccess tua)
	{
		double[] probs = this.getProbsFromPool(this.getLength()+1);
		BitSet bs = this.makeAllHeadCandidates(pos, thread, pa);
		double max = Double.NEGATIVE_INFINITY;
		for(int head = bs.nextSetBit(0);head >= 0;head = bs.nextSetBit(head+1))
		{
			this.setNewHead(pos, head, thread);
			max = Math.max(max,probs[head] = this.makeLogProbForThisConsideringAnnealing(pa, annealing, thread));
		}
		double sum = 0.0;
		for(int head = bs.nextSetBit(0);head >= 0;head = bs.nextSetBit(head+1))
		{sum += probs[head] = Math.exp(probs[head]-max);}
		double border = tua.nextDouble(thread);
		int choice = -1;
		for(int head = bs.nextSetBit(0);head >= 0;head = bs.nextSetBit(head+1))
		{
			border -= probs[head]/sum;
			if(border <= 0.0)
			{
				choice = head;
				break;
			}
		}
		if(choice < 0)
		{throw new IllegalStateException("probabilities did not sum to one.");}
		this.setNewHead(pos, choice, thread);
		this.returnProbsToPool(probs);
	}
	/* (non-Javadoc)
	 * @see com.gragra.sampling.nonproj.structures.nonprojective.singleroot.MHMHSingleRootNonProjDepdTree#resampleRoot(com.gragra.sampling.PopulationProbabilityAssigner, double, int, com.gragra.sampling.ThreadedUniformAccess)
	 */
	@Override
	protected void resampleRoot(PopulationProbabilityAssigner pa,
			double annealing, int thread, ThreadedUniformAccess tua)
	{
		double[] probs = this.getProbsFromPool(this.getLength()+1);
		double sizeCorrection = -Math.log(this.getLength());
		for(int i=0;i<this.getLength();++i)
		{
			if(tua.nextDouble(thread) < 0.001)
			{return;}
			if(i==this.getCurrentRoot(thread))
			{
				int oldRoot = i;
				int newRoot = tua.nextInt(thread, 0, this.getLength()-1);
				if(oldRoot == newRoot)
				{continue;}
				makeNewHead(pa, annealing, thread, tua, probs, -sizeCorrection, oldRoot, newRoot);
			}
			else
			{
				int oldRoot = this.getCurrentRoot(thread);
				int newRoot = i;
				makeNewHead(pa, annealing, thread, tua, probs, sizeCorrection, oldRoot, newRoot);
				
			}
		}
		this.returnProbsToPool(probs);
	}
	/**
	 * @param pa
	 * @param annealing
	 * @param thread
	 * @param tua
	 * @param probs
	 * @param sizeCorrection
	 * @param i
	 * @param oldRoot
	 * @param newRoot
	 * @throws IllegalStateException
	 */
	protected void makeNewHead(PopulationProbabilityAssigner pa,
			double annealing, int thread, ThreadedUniformAccess tua,
			double[] probs, double sizeCorrection, int oldRoot,
			int newRoot) throws IllegalStateException
	{
		int oldHeadOfNewRoot = this.getHeadPosition(newRoot, thread);
		double correction = sizeCorrection;
		//double oldProb = this.makeLogProbForThisConsideringAnnealing(pa, annealing, thread);
		BitSet bs = this.makeAllHeadCandidates(newRoot, thread, pa);
		double max = 0.0;
		for(int head=bs.nextSetBit(0);head>=0;head=bs.nextSetBit(head+1))
		{
			this.setNewHead(newRoot, head, thread);
			max = Math.max(max,probs[head] = this.makeLogProbForThisConsideringAnnealing(pa, annealing, thread));
		}
		double sum = 0.0;
		for(int head=bs.nextSetBit(0);head>=0;head=bs.nextSetBit(head+1))
		{sum += probs[head] = Math.exp(probs[head]-max);}
		correction += /*(Math.log(probs[oldHeadOfNewRoot])+max)*/-(Math.log(sum)+max);
		this.setNewHead(newRoot, this.getRootPosition(), thread);
		this.setNewHead(oldRoot, newRoot, thread);
		bs = this.makeAllHeadCandidates(oldRoot, thread, pa);
		max = Double.NEGATIVE_INFINITY;
		for(int head=bs.nextSetBit(0);head>=0;head=bs.nextSetBit(head+1))
		{
			this.setNewHead(oldRoot, head, thread);
			max = Math.max(max,probs[head] = this.makeLogProbForThisConsideringAnnealing(pa, annealing, thread));
		}
		sum = 0.0;
		for(int head=bs.nextSetBit(0);head>=0;head=bs.nextSetBit(head+1))
		{sum += probs[head] = Math.exp(probs[head]-max);}
		int choice = -1;
		double border = tua.nextDouble(thread);
		for(int head=bs.nextSetBit(0);head>=0;head=bs.nextSetBit(head+1))
		{
			border -= probs[head]/sum;
			if(border <= 0.0)
			{
				choice = head;
				break;
			}
		}
		if(choice < 0)
		{throw new IllegalStateException("Probabilites did not sum to 1.0.");}
		correction -= /*(Math.log(probs[choice])+max)*/-(Math.log(sum)+max);
		this.setNewHead(oldRoot, choice, thread);
		//double newProb = this.makeLogProbForThisConsideringAnnealing(pa, annealing, thread);
		if(Math.log(tua.nextDouble(thread)) > /*newProb-oldProb+*/correction)
		{
			this.setNewHead(oldRoot, this.getRootPosition(), thread);
			this.setNewHead(newRoot, oldHeadOfNewRoot, thread);
		}
	}
}